﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.Net.Forums
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;

    internal partial class SendReceiveHelper<TConnection> where TConnection : Connection
    {
        private sealed class ReceiveHeadersContext : FileOperationContext
        {
            private readonly bool receiveContents;
            private readonly long itemOffset;
            private readonly long itemCount;
            private readonly IInboundArticleInfo lastOldArticle;
            private Dictionary<string, InboundArticleIds> messageIdMap;
            private int insertCount;

            ////////////////////////////////////////////////////////////////////////////////////////////////////////////

            internal ReceiveHeadersContext(
                SendReceiveContext context,
                bool receiveContents,
                IProgress<ISendReceiveProgress> progress,
                TConnection connection,
                IForumProvider<TConnection> provider) :
                base(context, progress)
            {
                this.receiveContents = receiveContents;
                var highWaterMark = provider.GetHighWaterMark(connection, this.Group.Name);

                if (highWaterMark > 0)
                {
                    this.lastOldArticle = this.Root.GetInboundArticlesDescending(this.Group).FirstOrDefault();
                    var lastOldArticleNumber = lastOldArticle == null ? 0 : lastOldArticle.Number;

                    this.itemOffset =
                        Math.Max(lastOldArticleNumber, highWaterMark - this.Group.NewInboundArticlesReceiveLimit);
                    this.itemCount = highWaterMark - this.itemOffset;
                }
            }

            internal IInboundArticleInfo LastOldArticle
            {
                get { return this.lastOldArticle; }
            }

            internal IInboundArticleInfo SaveHeader(
                long number, string messageId, DateTime? date, string from, string subject, IList<string> references)
            {
                var messageIdMaxLength = this.Root.InboundArticleMessageIdMaxLength;
                InboundArticleIds reference;
                this.MessageIdMap.TryGetValue(
                    ValidationHelper.Truncate(GetDirectReference(references), messageIdMaxLength), out reference);

                var article =
                    new InboundArticle(
                        reference.Id, reference.OriginalId ?? reference.Id, this.receiveContents, this.Group)
                    {
                        Number = number,
                        DateUtc = ValidationHelper.LimitToSqlDateTime(date),
                        From = ValidationHelper.Truncate(from, this.Root.InboundArticleFromMaxLength),
                        Subject = ValidationHelper.Truncate(subject, this.Root.InboundArticleSubjectMaxLength),
                        MessageId = ValidationHelper.Truncate(messageId, messageIdMaxLength)
                    };

                IInboundArticleInfo result;

                // The message id can only be already in the map if the server sends two distinct messages with the same
                // message-ID, which is clearly illegal. We therefore do what the server should have done at his end, namely
                // discard the article.
                if (this.MessageIdMap.ContainsKey(article.MessageId))
                {
                    result = null;
                }
                else
                {
                    result = article;
                    this.MessageIdMap.Add(
                        article.MessageId, new InboundArticleIds() { Id = article.Id, OriginalId = article.OriginalId });
                    this.Root.InsertOnSubmit(article);
                    ++this.insertCount;
                }

                // TODO: This should better be done right at the start of the method. Doing so will ensure that a
                // header is always saved together with its content.
                if (this.insertCount >= Cache.MaxBatchSize)
                {
                    this.Submit();
                }

                this.ItemIndex = number;
                return result;
            }

            internal sealed override void Submit(int dummy = 0)
            {
                base.Submit(this.insertCount);
                this.insertCount = 0;
            }

            ////////////////////////////////////////////////////////////////////////////////////////////////////////////

            [SuppressMessage("Microsoft.Design", "CA1047:DoNotDeclareProtectedMembersInSealedTypes", Justification = "Override, CA bug?")]
            protected sealed override long ItemOffset
            {
                get { return this.itemOffset; }
            }

            [SuppressMessage("Microsoft.Design", "CA1047:DoNotDeclareProtectedMembersInSealedTypes", Justification = "Override, CA bug?")]
            protected sealed override long ItemCount
            {
                get { return this.itemCount; }
            }

            [SuppressMessage("Microsoft.Design", "CA1047:DoNotDeclareProtectedMembersInSealedTypes", Justification = "Override, CA bug?")]
            protected sealed override ISendReceiveProgress CreateProgress(int percent)
            {
                return new HeaderReceiveProgress(percent, this.Group.Name);
            }

            ////////////////////////////////////////////////////////////////////////////////////////////////////////////

            private Dictionary<string, InboundArticleIds> MessageIdMap
            {
                get
                {
                    if (this.messageIdMap == null)
                    {
                        this.messageIdMap = this.Root.GetMessageIdMap(this.Group);
                    }

                    return this.messageIdMap;
                }
            }

            private static string GetDirectReference(IList<string> references)
            {
                if ((references != null) && (references.Count > 0))
                {
                    return references[references.Count - 1];
                }

                // According to http://www.ietf.org/rfc/rfc5322.txt chapter 3.6.4, if the References header field is not
                // present, we should use the value of the In-Reply-To field. However, this field is not present in the
                // overview...
                return string.Empty;
            }
        }
    }
}
